﻿module FSharpScene
open System
open System.Xaml
open System.Xml
open System.IO
open Microsoft.FSharp.Quotations
open FSharp_Extensions
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Quotations.DerivedPatterns
open Microsoft.FSharp.Quotations.ExprShape
open Microsoft.FSharp.Reflection
open Microsoft.FSharp.Collections
open Microsoft.Xna.Framework
open Microsoft.Xna.Framework.Graphics
open Microsoft.Xna.Framework.Content
open PrintQuotation
open X3D_FSharpClass
open X3D_CSharp_Class
open Component

//il mio dizionario di route per semplicita di uso abbiamo usato stringe
let mutable route_map : Map<string,Quotations.Expr> = Map.empty

//dizionario stringa tipo, mi serve quando devo risalire al tipo della stringa quando mi trovo al difuori del suo contesto
let mutable dictionary : Map<string,Type> = Map.empty

//spesso ci troviamo nella situazione
//<Appearance> 
//    <Material DEF="myMaterial"/>  
//</Appearance>
//quindi non posso fare new Appearence(new Material()) dove material e' parametro del cotruttore appearence,
//ma material puo assere riferita da altri punto, quindi devo crearmi questa lista di let in maniera da essere sicuro
//che material tornera semplicemente il suo "def" ma nel frattempo carica la propria dichiarazione in questo let_expr
//let_expr
//  ..
//  let myMaterial = new Material()
//  ... 
//let myAppearence = new Appearence(myMaterial)
let mutable let_expr = Expr.Value(())

//expr della loadContent
let mutable load_content_expr = Expr.Value(())

//expr della draw
let mutable draw_expr = Expr.Value(())

let objectTree : X3D=
    let stream =
        new StreamReader("test.x3d")
    let xml = 
        new XmlTextReader(stream)
    in
    XamlServices.Load(xml) :?> X3D

//ho mi sono fatto pasasre una lista perche gli i figli degli oggetti sono all'interno di liste
let rec build_scene_expr (xml: obj list) =
    if xml = [] then Quotations.Expr.Value(())
    else
      match (List.head xml) with
      :? X3D as x ->
          let tags = List.map (fun p -> (p:>obj)) (Seq.toList(x.Global))
          in
          build_scene_expr (tags @ (List.tail xml))

      | :? Scene as x ->
          let tags = List.map (fun p -> (p:>obj)) (Seq.toList(x.Trans_Inf))
          in
          build_scene_expr (tags @ (List.tail xml))

      | :? Shape as x ->
          dictionary <- dictionary.Add(x.DEF, typeof<X3D_FSharpClass.Shape>)
          let _myShape = new Quotations.Var(x.DEF,typeof<X3D_FSharpClass.Shape>,false)
          let attributes = List.map (fun p -> build_scene_expr ([p])) (Seq.toList(x.Appear_IndexT))
          let newAmbient = List.tail xml
          let t_s = typeof<X3D_FSharpClass.Shape>

          let t_p_b = typeof<X3D_FSharpClass.Box>
          let t_p_a = typeof<X3D_FSharpClass.Appearance>
          let e_o = Expr.NewObject(t_s.GetConstructor([|t_p_b;t_p_a|]) , attributes)        

          Quotations.Expr.Let(_myShape,
                              e_o,
                              build_scene_expr newAmbient
                              )  
      | :? TimeSensor as x ->

          dictionary <- dictionary.Add(x.DEF, typeof<X3D_FSharpClass.TimeSensor>)

          let _myTimeSensor = new Quotations.Var(x.DEF,typeof<X3D_FSharpClass.TimeSensor>,false)
          let ambient = List.tail xml
          let loop = bool.Parse(x.loop)
          let cycle_int = Microsoft.FSharp.Core.float32.Parse(x.cycleInterval)
          Quotations.Expr.Let( _myTimeSensor,
                              <@@ new X3D_FSharpClass.TimeSensor(loop,
                                                                 cycle_int) @@>,
                              build_scene_expr ambient)
      | :? ColorInterpolator as x ->
          let def = x.DEF
          dictionary <- dictionary.Add(def, typeof<X3D_FSharpClass.ColorInterpolator>)  

          let _myColor = new Quotations.Var(x.DEF,typeof<X3D_FSharpClass.ColorInterpolator>,false)
          let ambient = List.tail xml
          let vec3_lst = x.keyValue.setToVector3List
          let ft32_lst = x.key.setToFloat32List
          Quotations.Expr.Let( _myColor,                            
                              <@@ new X3D_FSharpClass.ColorInterpolator(def,vec3_lst,ft32_lst) @@>, //da modificare il fraction
                              build_scene_expr ambient)
      | :? Box as x ->
            let def = x.DEF            
            let box_expr = <@new X3D_FSharpClass.Box(def, Vector3.One)@>
            let box_tp = typeof<X3D_FSharpClass.Box>
            let box_let = Quotations.Expr.Let(Quotations.Var(def, typeof<X3D_FSharpClass.Box>,false),box_expr,let_expr)
            let_expr <- box_let
            
            let model_name_expr = Quotations.Expr.Value("Box")
            let game_var = Quotations.Var("game",typeof<Game>,false)
            let game_expr = Quotations.Expr.Var(game_var)
            let game_tp = typeof<Game>
            let content = Quotations.Expr.PropertyGet(game_expr,game_tp.GetProperty("Content"),[])
            let content_expr = <@ (%%content:>(ContentManager)).Load<Model>("Box") @>

            //aggiungo il modello alla lista dei modelli da caricare nel load content
            let box_var = Quotations.Expr.Var(Quotations.Var(def, typeof<X3D_FSharpClass.Box>,false))
            let loaded_model = Quotations.Expr.Call(box_var,box_tp.GetMethod("set_Model"),[content_expr])
            load_content_expr <- Quotations.Expr.Sequential(loaded_model,load_content_expr)
            
            //aggiungo il nodo nella lista di oggetti da disegnare
            let dt_expr = Quotations.Expr.Var(Quotations.Var("dt",typeof<float32>,false))
            let draw = Quotations.Expr.Call(box_var,box_tp.GetMethod("Draw"),[dt_expr])
            draw_expr <- Quotations.Expr.Sequential(draw,draw_expr)

            box_var
      //chiadere a giuseppe del fatto che l'appearence non contiene DEF
      | :? Appearance as x ->
          let attributes = List.map (fun p -> build_scene_expr ([p])) (Seq.toList(x.Mater_ImgText))
          let t_p = typeof<X3D_FSharpClass.Appearance>
          let t_p_m = typeof<X3D_FSharpClass.Material>
          let app_val = Quotations.Expr.NewObject(t_p.GetConstructor([|t_p_m|]) , attributes)
          let app_var = Quotations.Var("Appearence1",typeof<X3D_FSharpClass.Appearance>,false)
          let app_let = Quotations.Expr.Let(app_var, app_val, let_expr)
          let_expr <- app_let
          Quotations.Expr.Var(app_var)

      | :? Material as x ->
          dictionary <- dictionary.Add(x.DEF, typeof<X3D_FSharpClass.Material>) 
          let def = x.DEF
          let material_val = <@new X3D_FSharpClass.Material(def, new Vector3(0.8f,0.8f,0.8f) )@>
          let material_let = Quotations.Expr.Let(Quotations.Var(def, typeof<X3D_FSharpClass.Material>,false),material_val,let_expr)
          let_expr <- material_let
          Quotations.Expr.Var(Quotations.Var(def, typeof<X3D_FSharpClass.Material>,false))

      //popolo il mio dizionario di route
      | :? ROUTE as x ->          
          let from_tp = dictionary.Item x.fromNode
          let from_pi = from_tp.GetProperty(x.fromField)
          let from_var = Quotations.Var(x.fromNode,from_tp,false)
          let from_expr = Quotations.Expr.Var(from_var)
          let from_get = Quotations.Expr.PropertyGet(from_expr, from_pi)

          let to_tp = dictionary.Item x.toNode
          let to_pi = to_tp.GetProperty(x.toField)
          let to_var = Quotations.Var(x.toNode,to_tp,true)
          let to_expr = Quotations.Expr.Var(to_var)
          let to_set = Quotations.Expr.PropertySet(to_expr, to_pi, from_get)
          if route_map.ContainsKey x.fromNode then
            let tmp = route_map.Item x.fromNode                
            route_map <- route_map.Add(x.fromNode, Quotations.Expr.Sequential(tmp,to_set))
          else
            route_map <- route_map.Add(x.fromNode, to_set)

          let ambient = List.tail xml
          build_scene_expr ambient    
      | _ -> 
        failwith "type error"

//mi costruisce il corpo dell'update quindi scorre l'interno albero e costruisce i nodi che necessitano di aggiornamento
//o di cambiamenti di stato nel tempo.
//i route verranno aggiunti immediatamente dopo aver costuito questo albero
let rec buildUpdateNodes (xml: obj list) =
    if xml = [] then <@@ () @@>
    else
        match (List.head xml) with
        :? X3D as x ->
            let tags = List.map (fun p -> (p:>obj)) (Seq.toList(x.Global))
            in
            buildUpdateNodes (tags @ (List.tail xml))

        | :? Scene as x ->
            let tags = List.map (fun p -> (p:>obj)) (Seq.toList(x.Trans_Inf))
            in
            buildUpdateNodes (tags @ (List.tail xml))

        | :? Shape as x ->
            let tags = List.map (fun p -> (p:>obj)) (Seq.toList(x.Appear_IndexT))
            in
            buildUpdateNodes (tags @ (List.tail xml))

        | :? TimeSensor as x ->
            let gametime_var = Quotations.Var("dt",typeof<float32>,false)
            let dt_expr = Quotations.Expr.Var(gametime_var)    
            
            let time_pi = typeof<X3D_FSharpClass.TimeSensor>.GetProperty("time")
            let var = Quotations.Var(x.DEF,typeof<X3D_FSharpClass.TimeSensor>,false)
            let time_expr = Quotations.Expr.Var(var)
            let time_prop_get = Quotations.Expr.PropertyGet(time_expr, time_pi)

            let time_sum = <@@ ((+) : float32 -> float32 -> float32) %%time_prop_get %%dt_expr  @@>
            let code = Quotations.Expr.PropertySet(time_expr, time_pi, time_sum)

            if x.loop = "true" then
                let if_then_else = 
                    Quotations.Expr.IfThenElse( 
                        <@@ %%time_prop_get > 10.0f @@>, 
                        Quotations.Expr.PropertySet(time_expr,time_pi,<@ 0.0f @>,[]), 
                        <@ () @>)
                Quotations.Expr.Sequential(
                    code,
                    Quotations.Expr.Sequential(if_then_else,buildUpdateNodes (List.tail xml)))
            else
                Quotations.Expr.Sequential(
                    code, 
                    buildUpdateNodes (List.tail xml))

        | :? ColorInterpolator as x ->
            buildUpdateNodes (List.tail xml)

        | :? Box as x ->
            buildUpdateNodes (List.tail xml)
        | :? Appearance as x ->
            buildUpdateNodes (List.tail xml)

        | :? ROUTE as x ->
            buildUpdateNodes (List.tail xml)
        | :? Material as x ->
            buildUpdateNodes (List.tail xml)
        | _ -> 
          failwith "type error"

let return_record () = Expr.NewRecord(typeof<Component>,[  Quotations.Expr.Var(Quotations.Var("update",typeof<float32->unit>,false));
                                                           Quotations.Expr.Var(Quotations.Var("draw",typeof<float32->unit>,false));
                                                           Quotations.Expr.Var(Quotations.Var("load_content",typeof<unit>,false))])
let draw () =  
            let draw = Quotations.Var("draw",typeof<float32->unit>,false)
            Expr.Let(draw,
                     Quotations.Expr.Lambda(new Quotations.Var("dt",typeof<float32>,false),draw_expr),
                     return_record ())
let update () =
            let update = Quotations.Var("update",typeof<float32->unit>,false)
            Expr.Let(update,
                     Quotations.Expr.Lambda(new Quotations.Var("dt",typeof<float32>,false),buildUpdateNodes [objectTree]),
                     draw ())
let loadContent () =
            let loadContent  = Quotations.Var("load_content",typeof<unit>,false)
            Expr.Let(loadContent,
                     load_content_expr,
                     update ())
let build_scene =        
        Quotations.Expr.Let(new Quotations.Var("build_scene",typeof<Game -> Component>,false),
                            Quotations.Expr.Lambda(new Quotations.Var("game",typeof<Game>,false),
                                                         (let tmp = build_scene_expr  [objectTree]
                                                          Quotations.Expr.Sequential(let_expr, 
                                                            Quotations.Expr.Sequential(tmp,loadContent ())))),
                            Quotations.Expr.Value(()))

let rec add_routes_to_scene expr (rs:Map<string,Quotations.Expr>) =
  match expr with
  |PropertySet (Some (e),ri,_,args) ->
    let str = println e
    if rs.ContainsKey str then
      let e' = add_routes_to_scene (rs.Item str) (rs.Remove str)
      Quotations.Expr.Sequential(expr,e')
    else
      expr
  |ExprShape.ShapeCombination (obj,e_l) -> ExprShape.RebuildShapeCombination(obj,e_l |> List.map (fun e -> add_routes_to_scene e rs))
  |ExprShape.ShapeLambda (obj,e) -> Quotations.Expr.Lambda(obj, add_routes_to_scene e rs)
  |ExprShape.ShapeVar (obj) -> Quotations.Expr.Var obj

let generate_code =
  add_routes_to_scene build_scene route_map